/******************************************************************************
 * Copyright 2022 The Airos Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#pragma once
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <utility>

namespace air {
namespace util {

template <typename T>
class BlockQueue {
 private:
  const size_t max_size_;
  mutable std::mutex lock_;
  std::condition_variable cond_;
  std::list<std::shared_ptr<T>> list_;

 public:
  explicit BlockQueue(size_t size);
  ~BlockQueue() = default;
  size_t Length() const;
  bool PushData(std::shared_ptr<T> data);
  std::shared_ptr<T> PopData(int timeout_ms = -1);
};

template <typename T>
BlockQueue<T>::BlockQueue(size_t size) : max_size_(size < 2 ? 2 : size) {}

template <typename T>
size_t BlockQueue<T>::Length() const {
  std::lock_guard<std::mutex> lg(lock_);
  return list_.size();
}

template <typename T>
bool BlockQueue<T>::PushData(std::shared_ptr<T> data) {
  if (data == nullptr) {
    return false;
  }
  {
    std::lock_guard<std::mutex> lg(lock_);
    if (max_size_ <= list_.size()) {
      list_.pop_front();
    }
    list_.emplace_back(std::move(data));
  }
  cond_.notify_all();
  return true;
}

template <typename T>
std::shared_ptr<T> BlockQueue<T>::PopData(int timeout_ms) {
  std::unique_lock<std::mutex> ul(lock_);
  if (timeout_ms < 0) {
    cond_.wait(ul, [this]() { return !list_.empty(); });
  }
  if (timeout_ms > 0) {
    cond_.wait_for(ul, std::chrono::milliseconds(timeout_ms),
                   [this]() { return !list_.empty(); });
  }
  if (list_.empty()) {
    return nullptr;
  }
  auto res = std::move(list_.front());
  list_.pop_front();
  return res;
}

}  // namespace util
}  // namespace air
